;(function() {
  function WordCloud(config = {}) {
    const dtr = Math.PI / 180, // 基础值
      _ts = this

    var _tags = { node: [], info: [], keyMap: {} }, // node存放节点信息，info存放坐标信息，keyMap存放原数组数据
      _moveInter = null, // 记录当前的定时器
      _active = false, // 是否需要停下
      _direction = { x: 0, y: 0, z: 0, sincos: {} }, // 速度相关配置
      _container = null, // 容器
      _subscribers = [], // 发布订阅
      _setting = _mergeSetting(config) // 配置信息

    /**
     * api 开始旋转
     * @param {*} e
     */
    this.start = function(e) {
      _active = true

      // 如果是手动暂停的，开始的时候重置速度
      if (Math.abs(_direction.x) < 0.05 && Math.abs(_direction.y) < 0.05) {
        // 移动速度
        _direction.x = _setting.direction.x * _setting.tspeed
        _direction.y = _setting.direction.y * _setting.tspeed
        _direction.z = _setting.direction.z * _setting.tspeed
        _updateSinCos()
      }

      // 兜底处理，确保moveInterval唯一性
      // 多个setInterval速度将会变得不可控制
      clearInterval(_moveInter)
      _moveInter = setInterval(_update, _setting.moveTime || 60)
    }

    /**
     * api 暂停旋转
     *
     */
    this.stop = function(stopMove = false) {
      // 这里不清除定时器，因为瞬间停下感觉很僵硬
      _active = false
      _setting.move = stopMove
    }

    /**
     * 更新旋转速度等
     * @param {Object} config
     * @param {Number} config.x 非必填  x轴移动速度
     * @param {Number} config.y 非必填  y轴移动速度
     * @param {Number} config.z 非必填  z轴移动速度
     * @param {Number} config.tspeed 非必填  整体移动速度
     */
    this.changeSpeed = function({ x = null, y = null, z = null, tspeed = null }) {
      // 更新配置
      typeof x === 'number' && (_setting.direction.x = x)
      typeof y === 'number' && (_setting.direction.y = y)
      typeof z === 'number' && (_setting.direction.z = z)
      typeof tspeed === 'number' && (_setting.tspeed = tspeed)

      // 重新计算
      _direction.x = _setting.direction.x * _setting.tspeed
      _direction.y = _setting.direction.y * _setting.tspeed
      _direction.z = _setting.direction.z * _setting.tspeed
      _updateSinCos()
      // 防止传入的x，y为0后，下次更新不会自动启动
      _setting.move && _ts.start()
    }

    /**
     * 获取当前的配置
     */
    this.getCurrentSpeed = function() {
      return { x: _direction.x, y: _direction.y, z: _direction.z, tspeed: _setting.tspeed }
    }

    /**
     * 销毁节点
     */
    this.destroy = function() {
      _container.innerHTML = ''
      _tags = { node: [], info: [], keyMap: {} }
      _active = false
      _direction = { x: 0, y: 0, z: 0, sincos: {} }
      _container = null
      _subscribers = []
      _setting = null
      clearInterval(_moveInter)
    }

    /**
     * 添加事件订阅
     * @param {*} topic  触发的事件
     * @param {*} callback  回调函数
     */
    this.on = function(topic, callback) {
      if (!topic || !callback) {
        console.error('订阅失败，缺少事件/回调函数')
        publish('error', { message: '订阅失败，缺少事件/回调函数' })
        return false
      }
      let callbacks = _subscribers[topic]
      if (!callbacks) {
        _subscribers[topic] = [callback]
      } else {
        callbacks.push(callback)
      }
    }

    /**
     * 取消订阅
     */
    this.remove = function(topic) {
      if (!topic) {
        console.error('取消订阅失败，缺少事件')
        publish('error', { message: '取消订阅失败，缺少事件' })
        return false
      }
      let callbacks = _subscribers[topic]
      // 事件不存在
      if (!callbacks) return false
      else delete _subscribers[topic]
    }

    /**
     * 重置词云
     * @param {*} config
     */
    this.reset = function(config) {
      this.destroy()
      _setting = _mergeSetting(config)
      _init()
    }

    /**
     * 合并配置项
     * @param {*} config
     */
    function _mergeSetting(config) {
      return Object.assign(
        {},
        {
          radius: 200, // 圆直径
          innerRadius: config && config.radius ? config.radius - (config.innerRadius || 0) : 200, // 圆内半径，就是中间会留空
          list: [], // 词云数组
          move: true, // 是否默认运动
          tspeed: 1, // 移动速度的倍数
          target: '', // 挂载节点的名称 / 容器节点
          direction: { x: 1, y: 1, z: -1 }, // 移动速度
          moveTime: 60 // 每次间隔多少毫秒更新一次位置
        },
        config
      )
    }

    /**
     * 发布对应的事件
     * @param {*} topic
     * @param  {...any} args
     */
    function publish(topic, ...args) {
      let callbacks = _subscribers[topic] || []
      callbacks.forEach(callback => callback(...args))
    }

    /**
     * init 初始化配置，生成节点等
     */
    function _init() {
      if (typeof _setting.target === 'string') {
        _container = document.getElementById(_setting.target || '')
      } else {
        _container = _setting.target
      }

      if (!_isDom(_container)) {
        console.error('dom节点不存在')
        publish('error', { message: 'dom节点不存在' })
        return false
      }

      _container.classList.add('word-cloud')

      _container.style.position = 'relative'

      // 渲染tags
      _createTags()

      // 初始化tags定位，生成圆形
      _render()

      // 初始化旋转速度等
      _initSinCos()

      // 开始旋转
      _setting.move && _ts.start()

      // 绑定监听事件
      _bindListener()
    }

    /**
     * 初始化旋转速度
     */
    function _initSinCos() {
      // 移动速度
      _direction.x = _setting.direction.x * _setting.tspeed
      _direction.y = _setting.direction.y * _setting.tspeed
      _direction.z = _setting.direction.z * _setting.tspeed
      // 初始化旋转方位
      _updateSinCos()
    }

    /**
     * 判断对象是否html节点
     */
    function _isDom(dom) {
      if (typeof HTMLElement === 'object') {
        return dom instanceof HTMLElement
      } else {
        return dom && typeof dom === 'object' && dom.nodeType === 1 && typeof dom.nodeName === 'string'
      }
    }

    /**
     * 生成a标签
     */
    function _createTags() {
      if (Array.isArray(_setting.list)) {
        let key = new Date()
          .getTime()
          .toString()
          .slice(-5)
        _setting.list.forEach((item, index) => {
          let tagItem = document.createElement('a')
          tagItem.setAttribute('href', item.link || 'javascript:;')
          tagItem.setAttribute('key', key + '_' + index)
          item.className && tagItem.setAttribute('class', item.className)
          item.style && tagItem.setAttribute('style', _phraseStyle(item.style))
          tagItem.innerHTML = item.text
          _container.appendChild(tagItem)

          // 节点信息（定位，宽度等）
          _tags.info.push({
            offsetWidth: tagItem.offsetWidth,
            offsetHeight: tagItem.offsetHeight
          })
          // 实际的html节点
          _tags.node.push(tagItem)
          // 用于点击事件的回调
          _tags.keyMap[key + '_' + index] = item
        })
      }
    }

    /**
     * 解析style内容，驼峰转-
     * @param {*} style
     */
    function _phraseStyle(style) {
      const keys = Object.keys(style)
      const keyValue = keys.map(key => {
        const kebabCaseKey = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
        const value = `${style[key]}${typeof style[key] === 'number' ? 'px' : ''}`
        return `${kebabCaseKey}:${value};`
      })
      return `${keyValue.join('')}`
    }

    /**
     * 初始化标签的定位
     */
    function _render() {
      var phi = 0
      var theta = 0
      var max = _tags.info.length

      for (let i = 1; i <= max; i++) {
        let node = _tags.node[i - 1]
        let info = _tags.info[i - 1]
        phi = Math.acos(-1 + (2 * i - 1) / max)
        theta = Math.sqrt(max * Math.PI) * phi

        // 这里需要记录坐标，用于下次更新的时候
        info.cx = _setting.radius * Math.cos(theta) * Math.sin(phi)
        info.cy = _setting.radius * Math.sin(theta) * Math.sin(phi)
        info.cz = _setting.radius * Math.cos(phi)

        // 修改坐标值
        node.style.left = info.cx + _container.offsetWidth / 2 - info.offsetWidth / 2 + 'px'
        node.style.top = info.cy + _container.offsetHeight / 2 - info.offsetHeight / 2 + 'px'
      }
    }

    /**
     * 更新点坐标
     */
    function _update() {
      // 更新移动速度
      var { x, y, z } = _direction
      if (Math.abs(x) <= 0.05 && Math.abs(y) <= 0.05) {
        clearInterval(_moveInter)
        return false
      }

      if (!_active) {
        // clearInterval(moveInterval)
        x *= 0.8
        y *= 0.8
        _direction.x = x
        _direction.y = y
        _updateSinCos()
      }

      // 更新点坐标
      _updatePosition()
      // 更新z轴
      _depthSort()
    }

    /**
     * 更新元素位置
     */
    function _updatePosition() {
      var { sx, cx, sy, cy, sz, cz } = _direction.sincos
      var left = _container.offsetWidth / 2
      var top = _container.offsetHeight / 2
      _tags.info.forEach((item, index) => {
        let node = _tags.node[index]
        var rx1 = item.cx
        var ry1 = item.cy * cx + item.cz * -sx
        var rz1 = item.cy * sx + item.cz * cx

        var rx2 = rx1 * cy + rz1 * sy
        var ry2 = ry1
        var rz2 = rx1 * -sy + rz1 * cy

        var rx3 = rx2 * cz + ry2 * -sz
        var ry3 = rx2 * sz + ry2 * cz
        var rz3 = rz2

        item.cx = rx3
        item.cy = ry3
        item.cz = rz3

        // 每个元素基础值
        per = _setting.innerRadius / (_setting.innerRadius + rz3)

        item.scale = per // 放大
        item.alpha = per

        // 每个节点，赋值透明度
        item.alpha = (item.alpha - 0.6) * (10 / 6)

        node.style.left = item.cx + left - item.offsetWidth / 2 + 'px'
        node.style.top = item.cy + top - item.offsetHeight / 2 + 'px'

        // 缩放字体  缩放字体特别耗性能，不建议开启
        // node.style.fontSize = Math.ceil((parseFloat(node.style.fontSize) * item.scale) / 2) + 8 + 'px'

        node.style.filter = 'alpha(opacity=' + 100 * item.alpha + ')'
        node.style.opacity = item.alpha
      })
    }

    /**
     * 更新z轴位置
     */
    function _depthSort() {
      var i = 0
      var tmpArr = []

      for (i = 0; i < _tags.node.length; i++) {
        tmpArr.push(_tags.node[i])
      }

      tmpArr.sort(function(vItem1, vItem2) {
        if (vItem1.cz > vItem2.cz) {
          return -1
        } else if (vItem1.cz < vItem2.cz) {
          return 1
        } else {
          return 0
        }
      })

      for (i = 0; i < tmpArr.length; i++) {
        tmpArr[i].style.zIndex = i
      }
    }

    /**
     *
     * @param {*} a  x轴滚动方向  正数 从上到下滚动  负数从下到上
     * @param {*} b  y轴滚动返现  正数 从右到左   负数 从左到右
     * @param {*} c  顺时针旋转速度
     */
    function _updateSinCos() {
      var { x, y, z } = _direction
      _direction.sincos = {
        sx: Math.sin(x * dtr),
        cx: Math.cos(x * dtr),
        sy: Math.sin(y * dtr),
        cy: Math.cos(y * dtr),
        sz: Math.sin(z * dtr),
        cz: Math.cos(z * dtr)
      }
    }

    function _bindListener() {
      /**
       * 鼠标移入
       * @param {*} e
       */
      _container.onmouseover = function(e) {
        publish('mouseover', { event: e, target: e.target })
        // 容器添加隐藏
        _container.classList.add('word-cloud_hover')
        e.target.opacity = 1
        // 内部暂停移动
        _active && _ts.stop(true)
      }

      /**
       * 鼠标移出
       * @param {*} e
       */
      _container.onmouseleave = function(e) {
        // 修改球的反向
        var oEvent = window.event || e
        var mouseX = oEvent.clientX - (_container.getBoundingClientRect().left + _container.offsetWidth / 2)
        var mouseY = oEvent.clientY - (_container.getBoundingClientRect().top + _container.offsetHeight / 2)

        // 获取鼠标离开的方向
        var levelX = -Math.min(Math.max(-mouseY, -_setting.radius / 2), _setting.radius / 2) / _setting.radius
        var levelY = Math.min(Math.max(-mouseX, -_setting.radius / 2), _setting.radius / 2) / _setting.radius

        _direction.x = (levelX / Math.abs(levelX)) * Math.abs(_setting.direction.x) * _setting.tspeed
        _direction.y = (levelY / Math.abs(levelY)) * Math.abs(_setting.direction.y) * _setting.tspeed

        _container.classList.remove('word-cloud_hover')

        _updateSinCos()

        publish('mouseleave', { event: e, direction: _direction })

        _setting.move && _ts.start()
      }

      /**
       * 鼠标点击
       */
      _container.addEventListener('click', function(e) {
        let nodeName = e.target.nodeName.toLowerCase()
        let nodeKey = e.target.getAttribute('key') || null
        if (nodeName === 'a' && nodeKey) {
          publish('click', { target: e.target, data: _tags.keyMap[nodeKey] || {} })
        }
      })
    }

    _init() // 调用初始化方法
  }

  /**
   * 动态插入css
   */
  ;(function() {
    var style = document.createElement('style')
    style.type = 'text/css'
    style.rel = 'stylesheet'
    const wordCloudStyle = `
    .word-cloud {
      position: relative;
    }
    .word-cloud a {
      transition: opacity 0.1s ease-in-out;
      white-space: nowrap;
      position: absolute;
    }
    .word-cloud:hover a,
    .word-cloud_hover a {
      opacity: 0.2 !important;
    }

    .word-cloud:hover a:hover,
    .word-cloud_hover a:hover {
      opacity: 1 !important;
      font-size: 150% !important;
    }
    `
    try {
      //for Chrome Firefox Opera Safari
      style.appendChild(document.createTextNode(wordCloudStyle))
    } catch (ex) {
      //for IE
      style.styleSheet.cssText = wordCloudStyle
    }
    var head = document.getElementsByTagName('head')[0]
    head.appendChild(style)
  })()

  window.WordCloud = WordCloud
})()
